package org.hawk.gwt.ppc.scanner.utils;

import java.util.Iterator;
import java.util.List;

/**
 * Represents java method declaration. Allows to get all
 * the method attributes such as name, return type and arguments.
 * 
 * @author alex.bereznevatiy@gmail.com
 */
public class JavaMethod extends JavaScope {
	private String name;
	private String returnType;
	private String[] parametersTypes;
	private boolean varargs;
	
	JavaMethod(String name,JavaScope parent,int start,
			List<JavaVariable> vars, String returnType){
		this(name,parent,start,vars,returnType,false);//TODO: check for varargs
	}

	JavaMethod(String name,JavaScope parent,int start,
			List<JavaVariable> vars, String returnType,
			boolean varargs){
		super(parent,start,vars);
		this.name = name;
		this.returnType = returnType;
		this.parametersTypes = extractParametersTypes(vars);
		this.varargs = varargs;
	}

	private String[] extractParametersTypes(List<JavaVariable> vars) {
		String[] result = new String[vars.size()];
		Iterator<JavaVariable> iterator = vars.iterator();
		for(int i=0;i<vars.size();i++){
			JavaVariable param = iterator.next();
			param.setScope(this);
			result[i] = param.getRawType();
		}
		return result;
	}
	/**
	 * @return name of this method
	 */
	public String getName() {
		return name;
	}
	/**
	 * @return array of parameters types of this method
	 */
	public String[] getParametersTypes() {
		String[] result = new String[parametersTypes.length];
		for(int i=0;i<parametersTypes.length;i++){
			result[i] = resolveType(parametersTypes[i]);
		}
		return result;
	}
	/**
	 * @return return type of this method
	 */
	public String getReturnType(){
		return resolveType(returnType);
	}

	boolean matches(String name, String...parametersTypes) {
		return this.name.equals(name)&&paramsMatches(
				getParametersTypes(),parametersTypes);
	}

	private boolean paramsMatches(String[] expected,String[] search) {
		if(expected.length!=search.length&&!varargs){
			return false;
		}
		final int lastIndex = expected.length-1;
		int i=0;
		for(;i<search.length;i++){
			String param = null;
			boolean isLast = false;
			if(i<lastIndex){
				param = expected[i];
			}
			else {
				isLast = true;
				param = expected[lastIndex];
			}
			if(isLast&&paramsMatches(param,search[i],false)){
				// array as var args - valid result 
				// only if it is last param
				return i==(search.length-1);
			}
			if(!paramsMatches(param,search[i],isLast)){
				return false;
			}
		}
		return i>=lastIndex;
	}

	private boolean paramsMatches(String expected, String search,boolean last) {
		if(getParent()==null||
			!(getParent() instanceof JavaTypeScope)){
			// may be helpful for testing purposes
			return expected.equals(search);
		}
		JavaTypeScope parent = (JavaTypeScope)getParent();
		JavaClassDescriptor expectedType = parent.resolveTypeDescriptor(expected);
		if(varargs&&last){
			if(!expectedType.isArray())return false;
			expectedType = expectedType.getArrayElement();
		}
		return expectedType.getCompilationUnit().isAssignableFrom(
			parent.resolveTypeDescriptor(search).getCompilationUnit());
	}

	@Override
	public boolean equals(Object obj) {
		if(this==obj)return true;
		if(obj instanceof JavaMethod){
			// methods are equal if signatures are equal.
			return toString().equals(String.valueOf(obj));
		}
		return super.equals(obj);
	}

	@Override
	public String toString() {
		StringBuilder result = new StringBuilder();
		result.append(getName()).append('(');
		boolean first = true;
		for(String param : getParametersTypes()){
			if(first)first=false;
			else result.append(',');
			result.append(param);
		}
		result.append(')');
		return result.toString();
	}
}